
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;

import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JTree;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

import org.rsbot.event.listeners.PaintListener;
import org.rsbot.script.Script;
import org.rsbot.script.ScriptManifest;
import org.rsbot.script.wrappers.RSInterface;
import org.rsbot.script.wrappers.RSInterfaceChild;

@ScriptManifest(authors = {"joku.rules"}, category = "Development", name = "Interface Explorer 2", version = 0.1, description = "<html>\n<head></head>\n<body>\n<center><h2>Interface Explorer 2</h2></center>\n<p\n<b>Author:</b> joku.rules<p>\n <b>Version:</b> 0.1<p>\nThis script is used to fetch various interface data.\n</body>\n </html>")
public class InterfaceExplorer2 extends Script implements PaintListener {

    private class InterfaceTreeModel implements TreeModel {

        private final Object root = new Object();
        private final ArrayList<TreeModelListener> treeModelListeners = new ArrayList<TreeModelListener>();
        // only call getAllInterfaces() once per GUI update, because
        // otherwise closed interfaces might mess up the indexes
        private final ArrayList<RSInterfaceWrap> interfaces = new ArrayList<RSInterfaceWrap>();

        public void addTreeModelListener(final TreeModelListener l) {
            treeModelListeners.add(l);
        }

        private void fireTreeStructureChanged(final Object oldRoot) {
            treeModelListeners.size();
            final TreeModelEvent e = new TreeModelEvent(this,
                    new Object[]{oldRoot});
            for (final TreeModelListener tml : treeModelListeners) {
                tml.treeStructureChanged(e);
            }
        }

        public Object getChild(final Object parent, final int index) {
            if (parent == root) {
                return interfaces.get(index);
            } else if (parent instanceof RSInterfaceWrap) {
                return new RSInterfaceChildWrap(
                        ((RSInterfaceWrap) parent).wrapped.getChildren()[index]);
            } else if (parent instanceof RSInterfaceChildWrap) {
                return new RSInterfaceChildWrap(
                        ((RSInterfaceChildWrap) parent).wrapped.getChildren()[index]);
            }
            return null;
        }

        public int getChildCount(final Object parent) {
            if (parent == root) {
                return interfaces.size();
            } else if (parent instanceof RSInterfaceWrap) {
                return ((RSInterfaceWrap) parent).wrapped.getChildren().length;
            } else if (parent instanceof RSInterfaceChildWrap) {
                return ((RSInterfaceChildWrap) parent).wrapped.getChildren().length;
            }
            return 0;
        }

        public int getIndexOfChild(final Object parent, final Object child) {
            if (parent == root) {
                return interfaces.indexOf(child);
            } else if (parent instanceof RSInterfaceWrap) {
                return Arrays.asList(
                        ((RSInterfaceWrap) parent).wrapped.getChildren()).indexOf(((RSInterfaceChildWrap) child).wrapped);
            } else if (parent instanceof RSInterfaceChildWrap) {
                return Arrays.asList(
                        ((RSInterfaceChildWrap) parent).wrapped.getChildren()).indexOf(
                        ((RSInterfaceChildWrap) child).wrapped);
            }
            return -1;
        }

        public Object getRoot() {
            return root;
        }

        public boolean isLeaf(final Object o) {
            if (o instanceof RSInterfaceChildWrap) {
                return ((RSInterfaceChildWrap) o).wrapped.getChildren().length == 0;
            } else if (o instanceof RSInterfaceChildWrap) {
                return ((RSInterfaceChildWrap) o).wrapped.getChildren().length == 0;
            }
            return false;
        }

        public void removeTreeModelListener(final TreeModelListener l) {
            treeModelListeners.remove(l);
        }

        public boolean searchMatches(final RSInterfaceChild iface,
                final String contains) {
            return iface.getText().toLowerCase().contains(
                    contains.toLowerCase());
        }

        public void update(final String search) {
            interfaces.clear();

            for (final RSInterface face : iface.getAll()) {
                toBreak:
                for (final RSInterfaceChild child : face.getChildren()) {
                    if (searchMatches(child, search)) {
                        interfaces.add(new RSInterfaceWrap(face));
                        break;
                    }

                    for (final RSInterfaceChild component : child.getChildren()) {
                        if (searchMatches(component, search)) {
                            interfaces.add(new RSInterfaceWrap(face));
                            break toBreak;
                        }
                    }
                }
            }
            fireTreeStructureChanged(root);
        }

        public void valueForPathChanged(final TreePath path,
                final Object newValue) {
            // tree represented by this model isn't editable
        }
    }

    private class RSInterfaceChildWrap {

        public final RSInterfaceChild wrapped;

        public RSInterfaceChildWrap(final RSInterfaceChild wrapped) {
            this.wrapped = wrapped;
        }

        @Override
        public boolean equals(final Object o) {
            return o instanceof RSInterfaceChildWrap ? wrapped == ((RSInterfaceChildWrap) o).wrapped
                    : false;
        }

        @Override
        public String toString() {
            return "Child " + wrapped.getIndex();
        }
    }

    // these wrappers just add toString() methods
    private class RSInterfaceWrap {

        public final RSInterface wrapped;

        public RSInterfaceWrap(final RSInterface wrapped) {
            this.wrapped = wrapped;
        }

        @Override
        public boolean equals(final Object o) {
            return o instanceof RSInterfaceWrap ? wrapped == ((RSInterfaceWrap) o).wrapped
                    : false;
        }

        @Override
        public String toString() {
            return "Interface " + wrapped.getIndex();
        }
    }
    private JFrame window;
    private JTree tree;
    private InterfaceTreeModel treeModel;
    private JPanel infoArea;
    private JTextField searchBox;
    private Rectangle highlightArea = null;

    @Override
    public int loop() {
        while (window.isVisible()) {
            return 1000;
        }
        return -1;
    }

    public void onRepaint(final Graphics g) {
        if (highlightArea != null) {
            g.setColor(Color.ORANGE);
            g.drawRect(highlightArea.x, highlightArea.y, highlightArea.width,
                    highlightArea.height);
        }
    }

    @Override
    public boolean onStart(final Map<String, String> map) {
        window = new JFrame("Interface Explorer 2");

        treeModel = new InterfaceTreeModel();
        treeModel.update("");
        tree = new JTree(treeModel);
        tree.setRootVisible(false);
        tree.setEditable(false);
        tree.getSelectionModel().setSelectionMode(
                TreeSelectionModel.SINGLE_TREE_SELECTION);
        tree.addTreeSelectionListener(new TreeSelectionListener() {

            private void addInfo(final String key, final String value) {
                final JPanel row = new JPanel();
                row.setAlignmentX(Component.LEFT_ALIGNMENT);
                row.setLayout(new BoxLayout(row, BoxLayout.X_AXIS));

                for (final String data : new String[]{key, value}) {
                    final JLabel label = new JLabel(data);
                    label.setAlignmentY(Component.TOP_ALIGNMENT);
                    row.add(label);
                }
                infoArea.add(row);
            }

            public void valueChanged(final TreeSelectionEvent e) {
                final Object node = tree.getLastSelectedPathComponent();
                if (node == null || node instanceof RSInterfaceWrap) {
                    return;
                }
                // at this point the node can only be an instace of
                // RSInterfaceChildWrap
                // or of RSInterfaceChildWrap

                infoArea.removeAll();
                RSInterfaceChild iface = null;
                if (node instanceof RSInterfaceChildWrap) {
                    highlightArea = ((RSInterfaceChildWrap) node).wrapped.getArea();
                    iface = ((RSInterfaceChildWrap) node).wrapped;
                } else if (node instanceof RSInterfaceChildWrap) {
                    highlightArea = ((RSInterfaceChildWrap) node).wrapped.getArea();
                    iface = ((RSInterfaceChildWrap) node).wrapped;
                }

                addInfo("Type : ", "" + iface.getType());
                addInfo("SpecialType : ", "" + iface.getSpecialType());
                addInfo("Bounds Index : ", "" + iface.getBoundsArrayIndex());
                addInfo("Model ID : ", "" + iface.getModelID());
                addInfo("Texture ID : ", "" + iface.getBackgroundColor());
                addInfo("Text : ", "" + iface.getText());
                addInfo("Tooltip : ", "" + iface.getTooltip());
                addInfo("Select Action Name : ", "" + iface.getSelectedActionName());
                if (iface.getActions() != null) {
                    String actions = "";
                    for (final String action : iface.getActions()) {
                        if (actions.length() != 0) {
                            actions += "\n";
                        }
                        actions += action;
                    }
                    addInfo("Actions : ", actions);
                }
                addInfo("Component ID : ", "" + iface.getChildID());
                addInfo("Component Stack Size : ", "" + iface.getChildStackSize());

                infoArea.validate();
                infoArea.repaint();
            }
        });

        JScrollPane scrollPane = new JScrollPane(tree);
        scrollPane.setPreferredSize(new Dimension(250, 500));
        window.add(scrollPane, BorderLayout.WEST);

        infoArea = new JPanel();
        infoArea.setLayout(new BoxLayout(infoArea, BoxLayout.Y_AXIS));
        scrollPane = new JScrollPane(infoArea);
        scrollPane.setPreferredSize(new Dimension(250, 500));
        window.add(scrollPane, BorderLayout.CENTER);

        final ActionListener actionListener = new ActionListener() {

            public void actionPerformed(final ActionEvent e) {
                treeModel.update(searchBox.getText());
                infoArea.removeAll();
                infoArea.validate();
                infoArea.repaint();
            }
        };

        final JPanel toolArea = new JPanel();
        toolArea.setLayout(new FlowLayout(FlowLayout.LEFT));
        toolArea.add(new JLabel("Filter:"));

        searchBox = new JTextField(20);
        searchBox.addActionListener(actionListener);
        toolArea.add(searchBox);

        final JButton updateButton = new JButton("Update");
        updateButton.addActionListener(actionListener);
        toolArea.add(updateButton);
        window.add(toolArea, BorderLayout.NORTH);

        window.pack();
        window.setVisible(true);
        return true;
    }
}
